<?php
/**
 * Created by PhpStorm.
 * User: bzg
 * Date: 2019/11/27
 * Time: 11:23
 */

namespace App\HttpController;

use App\Common\Exception\ServiceRunTimeException;
use EasySwoole\Http\AbstractInterface\Controller;
use EasySwoole\Template\Render;
use EasySwoole\Validate\Validate;

/**
 * 全局基础控制器
 * Class BaseController
 * @package App\HttpController
 */
class BaseController extends Controller
{
    /**
     * 验证方法对应的默认提示信息
     * @var array
     */
    protected $validate_msg = [
        'activeUrl'             => '无法成功通信',
        'alpha'                 => '只能是字母',
        'alphaNum'              => '只能是字母或数字',
        'alphaDash'             => '只能是字母或汉字',
        'between'               => '只能在{%1}和{%2}之间',
        'bool'                  => '只能是布尔值',
        'decimal'               => '只能是{%1}位的小数',
        'dateBefore'            => '只能在{%1}日期之前',
        'dateAfter'             => '只能在{%1}日期之后',
        'equal'                 => '必须等于{%1}',
        'float'                 => '必须是个小数',
        'func'                  => '验证失败',
        'inArray'               => '必须在{%1}中',
        'integer'               => '必须是一个整数',
        'isIp'                  => '必须是一个有效的IP地址',
        'notEmpty'              => '不能为空',
        'numeric'               => '必须是个数字',
        'notInArray'            => '不能在{%1}中',
        'length'                => '长度必须为{%1}位',
        'lengthMax'             => '长度最大为{%1}位',
        'lengthMin'             => '长度最小为{%1}位',
        'betweenLen'            => '长度只能在{%1}到{%2}之间',
        'max'                   => '必须小于{%1}',
        'min'                   => '必须大于{%1}',
        'money'                 => '必须是个有效的金额',
        'regex'                 => '格式错误',
        'required'              => '必须存在',
        'timestamp'             => '必须是一个时间戳',
        'timestampBeforeDate'   => '必须在{%1}之前',
        'timestampAfterDate'    => '必须在{%1}之后',
        'timestampBefore'       => '必须在{%1}时间戳之前',
        'timestampAfter'        => '必须在{%1}时间戳之后',
        'url'                   => '必须是一个链接',
        'allDigital'            => '必须是一个链接'
    ];

    function index()
    {
        // TODO: Implement index() method.
    }

    /**
     * 方法未找到处理
     * @param null|string $action
     */
    protected function actionNotFound(?string $action)
    {
        $this->response()->withStatus(404);
        $file = EASYSWOOLE_ROOT.'/vendor/easyswoole/easyswoole/src/Resource/Http/404.html';
        if(!is_file($file)){
            $file = EASYSWOOLE_ROOT.'/src/Resource/Http/404.html';
        }
        $this->response()->write(file_get_contents($file));
    }

    /**
     * 服务层异常处理
     * @param \Throwable $throwable
     * @throws \Throwable
     */
    function onException(\Throwable $throwable): void
    {
        // 如果是运行时异常则返回错误json
        if($throwable instanceof ServiceRunTimeException) {
            $this->error($throwable->getMessage());
            return;
        }
        // 剩下的交给父类处理
        parent::onException($throwable);
    }

    /**
     * 验证参数
     * @param array $params 需要验证的参数
     * @param array $check 验证规则
     *  [
     *      // 自带的验证方法名，参数使用冒号引用，如果参数是数组，则用&隔开元素,每个方法用|隔开，闭包使用func:闭包名
     *      // 闭包的传入参数为EasySwoole\Spl\SplArray对象，需要用get方法获取指定值
     *
     *      '参数名|参数备注' => 'alpha|between:1,10|func:闭包名|inArray:1&2&3'
     * ]
     * @param array $closures 闭包数组
     * @return array 返回验证的参数数组
     * @throws ServiceRunTimeException
     */
    protected function validateParams(array $params, array $check, array $closures = [])
    {
        $validate = new Validate();
        // 组装验证规则
        foreach ($check as $k => $r) {
            // $k 为需验证的参数名|备注
            $key = explode('|', $k);
            $name = $key[0]; // 参数名
            $desc = $key[1] ?? $key[0]; // 参数备注

            // $r 为验证规则，即自带的验证方法名，参数使用冒号引用，每个方法用|隔开，闭包使用func:闭包名
            // 如 alpha|between:1,10|func:闭包名
            $cur = $validate->addColumn($name, $desc);
            $rules = explode('|', $r);
            foreach ($rules as $rule) {

                // 使用冒号隔开方法名和参数
                $mp = explode(':',$rule);
                $method = $mp[0]; // 方法名
                $params_str = $mp[1] ?? ''; // 参数字符串
                // 使用逗号隔开参数
                $params_arr = empty($params_str) ? [] : explode(',',$params_str);

                // 设置可选参数，不需要参数
                if($method != 'optional') {
                    $msg = $this->validate_msg[$method];
                    // 替换消息中的占位符,且解析数组,转换参数类型
                    if(!empty($params_arr)) {
                        $index = 1;
                        foreach ($params_arr as &$v) {
                            $msg = str_replace('{%'.$index++.'}',$v,$msg);
                            // 转换参数类型
                            // 如果参数中有数组，数组用&符号隔开每个元素，如1&2&3 = [1,2,3]
                            if(strstr($v,'&')){
                                $v = explode('&',$v);
                            }
                            // 转整型或者浮点型
                            if(is_numeric($v)) {
                                $v = strstr($v,'.') === false ? (int) $v : (float) $v;
                            }
                        }
                    }
                    $params_arr[] = $msg; // 将提示信息写入最后一个参数
                }

                // 添加验证方法
                if($method == 'func') {
                    // 闭包验证,在闭包数组中找对应的闭包,闭包的传入参数为EasySwoole\Spl\SplArray对象，需要用get方法获取指定值
                    if(!($closures[$params_arr[0]] instanceof \Closure)) {
                        throw new ServiceRunTimeException('没有找到对应的闭包');
                    }
                    $cur = $cur->$method($closures[$params_arr[0]],$params_arr[1]);
                }else{
                    if($params_arr) {
                        $cur = $cur->$method(... $params_arr);
                    }else{
                        $cur = $cur->$method();
                    }
                }
            }
        }
        // 验证
        if($this->validate($validate) === false) {
            throw new ServiceRunTimeException(
                $validate->getError()->getFieldAlias()
                . $validate->getError()->getErrorRuleMsg()
                );
        }
        return $params;
    }

    /**
     * 返回自定义JSON
     * @param int $code 响应码
     * @param [type] $data 响应数据
     * @param string $msg 响应消息
     * @return
     */
    protected function extendJson(int $code, $data, string $msg)
    {
        $this->response()->withAddedHeader('Content-Type','json');
        return $this->response()->write(
            json_encode([
                'code' => $code,
                'data' => $data,
                'msg' => $msg
            ],JSON_UNESCAPED_UNICODE)
        );
    }

    /**
     * 返回成功json
     * @param [type] $data
     * @param int $code
     * @param string $msg
     * @return
     */
    protected function success($data, $code = 1, $msg = '')
    {
        return $this->extendJson($code, $data, $msg);
    }

    /**
     * 返回失败json
     * @param string $msg
     * @param int $code
     * @param [type] $data
     * @return
     */
    public function error(string $msg, $code = 0, $data = null)
    {
        return $this->extendJson($code, $data, $msg);
    }


    /**
     * 获取请求参数
     * @param string|array $name 参数名或者参数名=>默认值的数组
     * @param string|null $method
     * @return array|mixed|null|object
     */
    protected function param($name, string $method = null)
    {
        switch (strtolower($method)) {
            case 'post':
                $all_param = $this->request()->getParsedBody();
                break;
            case 'get':
                $all_param = $this->request()->getQueryParams();
                break;
            default:
                $all_param = $this->request()->getRequestParam();
        }
        if(!empty($name)) {
            // 如果是数组，数字键则值为参数名，字符串键则键为参数名，值为默认值
            if(is_array($name)) {
                $res_param = [];
                foreach ($name as $k => $v) {
                    if(is_int($k)) {
                        $res_param[$v] = empty($all_param[$v]) ? null : $all_param[$v];
                    }else{
                        $res_param[$k] = empty($all_param[$k]) ? $v : $all_param[$k];
                    }
                }
                return $res_param;
            }else{
                return empty($all_param[$name]) ? null : $all_param[$name];
            }
        }
        return $all_param;
    }

    /**
     * 获取get参数
     * @param $name
     * @return array|mixed|null|object
     */
    protected function get($name = '')
    {
        return $this->param($name, 'GET');
    }

    /**
     * 获取post参数
     * @param $name
     * @return array|mixed|null|object
     */
    protected function post($name = '')
    {
        return $this->param($name, 'POST');
    }

    /**
     * 返回视图
     * @param string $template
     * @param array $data
     */
    protected function view(string $template, array $data = [])
    {
        $this->response()->write(Render::getInstance()->render($template,$data));
    }

    /**
     * 重定向
     * @param $url
     */
    protected function redirect($url)
    {
        $this->response()->withAddedHeader('location',$url)->withStatus(301);
    }

}